{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "Auteur: [Wayde Herman](https://www.linkedin.com/in/wayde-herman-10986685/) depuis [kaggle](https://www.kaggle.com/waydeherman/tutorial-categorical-encoding). Traduit et édité par [Ousmane Cissé](https://www.linkedin.com/in/ousmane-cissé).  \n",
    "<center>\n",
    "Ce matériel est soumis aux termes et conditions de la licence [Creative Commons CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/).  \n",
    "L'utilisation gratuite est autorisée à des fins non commerciales."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "# Encodage des caractéristiques catégorielles\n",
    "\n",
    "## Introduction:\n",
    "\n",
    "Dans la plupart des problèmes de science des données, nos ensembles de données contiendront des caractéristiques catégorielles. Les entités catégorielles contiennent un nombre fini de valeurs discrètes. La façon dont nous représentons ces caratéristiques aura un impact sur les performances de notre modèle. Comme dans d'autres aspects de l'apprentissage automatique, il n'y a pas de \"baguette magique\". Déterminer la bonne approche, spécifique à notre modèle et à nos données, fait partie du défi.\n",
    "\n",
    "Ce tutoriel vise à couvrir quelques-unes de ces méthodes. Nous commençons par couvrir une technique simple avant d'aborder des approches plus complexes et moins connues.\n",
    "\n",
    "**Liste des méthodes couvertes**:\n",
    "1. One-Hot Encoding (Encodage 1 parmi n)\n",
    "2. Feature Hashing (Hachage de caractéristiques)\n",
    "3. Binary Encoding (Encodage binaire)\n",
    "4. Target Encoding (Encodage de la cible)\n",
    "5. Weight of Evidence (Poids de l'élement preuve)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-02-16T10:46:52.352445Z",
     "start_time": "2020-02-16T10:46:31.179058Z"
    }
   },
   "outputs": [],
   "source": [
    "# Import required libraries:\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn.model_selection import cross_val_score, train_test_split\n",
    "from sklearn.metrics import roc_auc_score\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "import os\n",
    "import warnings\n",
    "warnings.simplefilter(action='ignore', category=FutureWarning)\n",
    "\n",
    "# Set our random seed:\n",
    "SEED = 17\n",
    "PATH_TO_DIR = 'data/'\n",
    "\n",
    "print(os.listdir(PATH_TO_DIR))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Pour ce tutoriel, nous utiliserons l'ensemble de données «[Amazon.com Employee Access Challenge](https://www.kaggle.com/c/amazon-employee-access-challenge)». Cet ensemble de données de classification binaire est composé de caractéristiques strictement catégorielles, qui sont déjà converties en chiffres, ce qui en fait un choix particulièrement approprié pour explorer diverses techniques de codage. Pour simplifier les choses, nous n'utiliserons qu'un sous-ensemble des caractéristiques de cette démonstration."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-02-16T10:46:58.159018Z",
     "start_time": "2020-02-16T10:46:57.345993Z"
    },
    "_cell_guid": "79c7e3d0-c299-4dcb-8224-4455121ee9b0",
    "_uuid": "d629ff2d2480ee46fbb7e2d37f6b5fab8052498a"
   },
   "outputs": [],
   "source": [
    "# Import data:\n",
    "train = pd.read_csv(PATH_TO_DIR + 'train.csv')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-02-16T10:46:58.941102Z",
     "start_time": "2020-02-16T10:46:58.909242Z"
    }
   },
   "outputs": [],
   "source": [
    "y = train['ACTION']\n",
    "train = train[['RESOURCE', 'MGR_ID', 'ROLE_FAMILY_DESC', 'ROLE_FAMILY', 'ROLE_CODE']]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous comparerons les différences de ces méthodes de codage à la fois sur un modèle linéaire et sur un modèle basé sur des arbres. Ceux-ci représentent deux familles de modèles qui ont des comportements contrastés en ce qui concerne les différentes représentations d'entités."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-02-16T10:47:00.924109Z",
     "start_time": "2020-02-16T10:47:00.906551Z"
    }
   },
   "outputs": [],
   "source": [
    "logit = LogisticRegression(random_state=SEED)\n",
    "rf = RandomForestClassifier(random_state=SEED)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-02-16T10:47:01.773684Z",
     "start_time": "2020-02-16T10:47:01.735393Z"
    }
   },
   "outputs": [],
   "source": [
    "# Split dataset into train and validation subsets:\n",
    "X_train, X_val, y_train, y_val = train_test_split(train, y, test_size=0.2, random_state=SEED)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-02-16T10:47:03.690033Z",
     "start_time": "2020-02-16T10:47:03.661091Z"
    }
   },
   "outputs": [],
   "source": [
    "# We create a helper function to get the scores for each encoding method:\n",
    "def get_score(model, X, y, X_val, y_val):\n",
    "    model.fit(X, y)\n",
    "    y_pred = model.predict_proba(X_val)[:,1]\n",
    "    score = roc_auc_score(y_val, y_pred)\n",
    "    return score"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-02-16T10:47:04.681445Z",
     "start_time": "2020-02-16T10:47:04.625134Z"
    }
   },
   "outputs": [],
   "source": [
    "# Lets have a quick look at our data:\n",
    "X_train.head(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-02-16T10:47:09.936333Z",
     "start_time": "2020-02-16T10:47:09.893424Z"
    }
   },
   "outputs": [],
   "source": [
    "X_train.info()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-02-16T10:47:11.151378Z",
     "start_time": "2020-02-16T10:47:11.120458Z"
    }
   },
   "outputs": [],
   "source": [
    "# Discover the number of categories within each categorical feature:\n",
    "len(X_train.RESOURCE.unique()), len(X_train.MGR_ID.unique()), len(X_train.ROLE_FAMILY_DESC.unique()), len(X_train.ROLE_FAMILY.unique()),len(X_train.ROLE_CODE.unique())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-02-16T10:47:12.30169Z",
     "start_time": "2020-02-16T10:47:12.277781Z"
    }
   },
   "outputs": [],
   "source": [
    "# Create a list of each categorical column name:\n",
    "columns = [i for i in X_train.columns]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-02-16T10:47:13.121818Z",
     "start_time": "2020-02-16T10:47:13.102726Z"
    }
   },
   "outputs": [],
   "source": [
    "columns"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Avant de commencer, examinons la vitesse et les performances de l'entraînement de ces modèles sans encodage de caractéristiques."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-02-16T09:22:58.723386Z",
     "start_time": "2020-02-16T09:22:58.627509Z"
    }
   },
   "outputs": [],
   "source": [
    "%%time\n",
    "baseline_logit_score = get_score(logit, X_train, y_train, X_val, y_val)\n",
    "print('Logistic Regression score without feature engineering:', baseline_logit_score)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-02-16T09:23:01.763607Z",
     "start_time": "2020-02-16T09:23:00.87044Z"
    }
   },
   "outputs": [],
   "source": [
    "%%time\n",
    "baseline_rf_score = get_score(rf, X_train, y_train, X_val, y_val)\n",
    "print('Random Forest score without feature engineering:', baseline_rf_score)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "## One-Hot Encoding:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "La première méthode que nous aborderons est celle que vous connaissez sans doute. Le One-hot encoding transforme 1 caratéristique catégorielle composée de m catégories en m* caractéristiques distinctes avec des valeurs de 0 ou 1.\n",
    "\n",
    "Il existe 2 façons de mettre en œuvre unOne-Hot Encoding, avec pandas ou scikit-learn. Dans ce tutoriel, nous avons choisi d'utiliser ce dernier.  \n",
    "\n",
    "*En fait, il est considéré comme plus correct d'élargir m catégories en (m - 1) caractéristiques distinctes. La raison en est double. Premièrement, si les valeurs des (m - 1) caractéristiques sont connues, la m-ième caractéristiques peut être déduite et deuxièmement parce que l'inclusion de la m-ième entité peut rendre certains modèles linéaires instables. Plus d'informations à ce sujet peuvent être trouvées [ici](https://www.algosome.com/articles/dummy-variable-trap-regression.html). En pratique, je pense que cela dépend de votre modèle. Certains modèles non linéaires fonctionnent mieux avec les caractéristiques m."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-02-16T09:23:09.234993Z",
     "start_time": "2020-02-16T09:23:09.218092Z"
    }
   },
   "outputs": [],
   "source": [
    "from sklearn.preprocessing import OneHotEncoder\n",
    "\n",
    "one_hot_enc = OneHotEncoder(sparse=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-02-16T09:24:57.049525Z",
     "start_time": "2020-02-16T09:23:10.393678Z"
    }
   },
   "outputs": [],
   "source": [
    "print('Original number of features: \\n', X_train.shape[1], \"\\n\")\n",
    "data_ohe_train = (one_hot_enc.fit_transform(X_train))\n",
    "data_ohe_val = (one_hot_enc.transform(X_val))\n",
    "print('Features after OHE: \\n', data_ohe_train.shape[1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-02-16T09:26:02.918421Z",
     "start_time": "2020-02-16T09:25:31.747943Z"
    }
   },
   "outputs": [],
   "source": [
    "%%time\n",
    "ohe_logit_score = get_score(logit, data_ohe_train, y_train, data_ohe_val, y_val)\n",
    "print('Logistic Regression score with one-hot encoding:', ohe_logit_score)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "ohe_rf_score = get_score(rf, data_ohe_train, y_train, data_ohe_val, y_val)\n",
    "print('Random Forest score with one-hot encoding:', ohe_rf_score)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Comme nous pouvons le voir, bien que les performances du modèle se soient améliorées, l'entraînement a également pris plus de temps. Cela est dû à l'augmentation du nombre de caractéristiques. Les coûts de calcul ne sont pas le seul problème associé à l'augmentation des dimensions. Un ensemble de données avec plus de caractéristiques nécessitera un modèle avec plus de paramètres qui à son tour nécessitera plus de données pour entraîner ces paramètres. Dans de nombreux cas, tels que les compétitions de kaggle, la taille de nos données est fixe et, par conséquent, la dimensionnalité de nos données devrait toujours être une préoccupation.\n",
    "\n",
    "Une façon de gérer la dimensionnalité élevée consiste à compresser les caractéristiques. Le hachage de caractéristiques, que nous aborderons ensuite, en est un exemple."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "en"
   },
   "source": [
    "## Feature Hashing:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Le hachage de caractéristiques mappe chaque catégorie d'une caractéristique catégorielle à un entier dans une plage prédéterminée. Cette plage de sortie est plus petite que la plage d'entrée, de sorte que plusieurs catégories peuvent être mappées sur le même entier. Le hachage des caractéristiques est très similaire au One-Hot Encoding, mais avec un contrôle sur les dimensions de sortie.\n",
    "\n",
    "Pour implémenter le hachage des caractéristiques en python, nous pouvons utiliser category_encoder, une bibliothèque contenant des encodeurs de catégorie compabitables sklearn."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-02-16T10:48:44.541827Z",
     "start_time": "2020-02-16T10:48:44.519991Z"
    }
   },
   "outputs": [],
   "source": [
    "# Install category_encoders:\n",
    "# !pip install category_encoders\n",
    "# or !conda install -c conda-forge category_encoders -y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-02-16T10:45:52.698034Z",
     "start_time": "2020-02-16T10:45:49.654694Z"
    }
   },
   "outputs": [],
   "source": [
    "from category_encoders import HashingEncoder"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "La taille des dimensions de sortie est contrôlée par la variable n_components. Cela peut être traité comme un hyperparamètre."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-02-16T11:03:54.735157Z",
     "start_time": "2020-02-16T11:03:54.715779Z"
    }
   },
   "outputs": [],
   "source": [
    "n_components_list = [100, 500, 1000, 5000, 10000]\n",
    "n_components_list_str = [str(i) for i in n_components_list]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fh_logit_scores = []\n",
    "\n",
    "# Iterate over different n_components:\n",
    "for n_components in n_components_list:\n",
    "    \n",
    "    hashing_enc = HashingEncoder(cols=columns, n_components=n_components).fit(X_train, y_train)\n",
    "    \n",
    "    X_train_hashing = hashing_enc.transform(X_train.reset_index(drop=True))\n",
    "    X_val_hashing = hashing_enc.transform(X_val.reset_index(drop=True))\n",
    "    \n",
    "    fe_logit_score = get_score(logit, X_train_hashing, y_train, X_val_hashing, y_val)\n",
    "    fh_logit_scores.append(fe_logit_score)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-02-16T11:32:55.375586Z",
     "start_time": "2020-02-16T11:31:55.741Z"
    }
   },
   "outputs": [],
   "source": [
    "plt.figure(figsize=(8, 5))\n",
    "plt.plot(n_components_list_str, fh_logit_scores, linewidth=3)\n",
    "plt.title('n_compontents vs roc_auc for feature hashing with logistic regression')\n",
    "plt.xlabel('n_components')\n",
    "plt.ylabel('score')\n",
    "plt.show;"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Comme nous pouvons le voir, les performances du modèle de régression logistique s'améliorent à mesure que le nombre de composants augmente. Mais regardons l'effet de la réduction des dimensions sur un modèle de forêt aléatoire."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hashing_enc = HashingEncoder(cols=columns, n_components=10000).fit(X_train, y_train)\n",
    "\n",
    "X_train_hashing = hashing_enc.transform(X_train.reset_index(drop=True))\n",
    "X_val_hashing = hashing_enc.transform(X_val.reset_index(drop=True))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_hashing.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "hashing_logit_score = get_score(logit, X_train_hashing, y_train, X_val_hashing, y_val)\n",
    "print('Logistic Regression score with feature hashing:', hashing_logit_score)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "hashing_rf_score = get_score(rf, X_train_hashing, y_train, X_val_hashing, y_val)\n",
    "print('Random Forest score with feature hashing:', hashing_rf_score)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Cela s'améliore! Comme nous l'avons peut-être deviné, la réduction du nombre de caractéristiques améliore les performances des modèles basés sur les arbres."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "en"
   },
   "source": [
    "## Binary Encoding:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "L' encodage binaire implique la conversion de chaque catégorie en un code binaire, par exemple 2 devient 11 et 3 devient 100, puis divise la chaîne binaire résultante en colonnes.\n",
    "\n",
    "Cela peut être plus facile à comprendre avec un exemple:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-02-16T11:00:54.439681Z",
     "start_time": "2020-02-16T11:00:54.294954Z"
    }
   },
   "outputs": [],
   "source": [
    "# Create example dataframe with numbers ranging from 1 to 5:\n",
    "example_df = pd.DataFrame([1,2,3,4,5], columns=['example'])\n",
    "\n",
    "from category_encoders import BinaryEncoder\n",
    "\n",
    "example_binary = BinaryEncoder(cols=['example']).fit_transform(example_df)\n",
    "\n",
    "example_binary"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "L'encodage binaire est clairement très similaire au hachage de caractéristiques, mais beaucoup plus restreint. En pratique, l'utilisation du hachage de caractéristiques est souvent conseillée par rapport à l'encodage binaire en raison du contrôle que vous avez sur les dimensions de sortie."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "binary_enc = BinaryEncoder(cols=columns).fit(X_train, y_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_binary = binary_enc.transform(X_train.reset_index(drop=True))\n",
    "X_val_binary = binary_enc.transform(X_val.reset_index(drop=True))\n",
    "# note: category_encoders implementations can't handle shuffled datasets. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('Features after Binary Encoding: \\n', X_train_binary.shape[1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "be_logit_score = get_score(logit, X_train_binary, y_train, X_val_binary, y_val)\n",
    "print('Logistic Regression score with binary encoding:', be_logit_score)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "binary_rf_score = get_score(rf, X_train_binary, y_train, X_val_binary, y_val)\n",
    "print('Random Forest score with binary encoding:', binary_rf_score)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "en"
   },
   "source": [
    "## Target Encoding:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Le Target Encoding est le premier de nos encodeurs bayésiens. Il s'agit d'une famille d'encodeurs qui prennent en compte les informations sur la variable cible. Le Target Encoding peut se référer à un codeur qui considère la corrélation statistique entre les catégories individuelles d'une caractéristique catégorielle. Dans ce tutoriel, nous examinerons uniquement les encodeurs cibles qui se concentrent sur la relation entre chaque catégorie et la moyenne de la cible, car il s'agit de la variation la plus couramment utilisée du Target Encoding."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from category_encoders import TargetEncoder\n",
    "\n",
    "targ_enc = TargetEncoder(cols=columns, smoothing=8, min_samples_leaf=5).fit(X_train, y_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_te = targ_enc.transform(X_train.reset_index(drop=True))\n",
    "X_val_te = targ_enc.transform(X_val.reset_index(drop=True))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_te.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "te_logit_score = get_score(logit, X_train_te, y_train, X_val_te, y_val)\n",
    "print('Logistic Regression score with target encoding:', te_logit_score)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "te_rf_score = get_score(rf, X_train_te, y_train, X_val_te, y_val)\n",
    "print('Random Forest score with target encoding:', te_rf_score)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "En raison de l'utilisation de la variable cible, la fuite de données et le sur-ajustement sont une préoccupation majeure. L'implémentation de category_encoders a deux façons prédéfinies de régulariser les encodages, le smoothing «lissage» et les min_samples_leaf. Ces paramètres peuvent être traités comme des hyperparamètres.\n",
    "\n",
    "le «lissage» détermine la pondération de la moyenne de chaque catégorie avec la moyenne de l'ensemble de la variable catégorielle. Il s'agit d'empêcher l'influence de moyens peu fiables provenant de catégories à faible taille d'échantillon.\n",
    "\n",
    "'min_ samples_leaf' est le nombre minimum d'échantillons dans une catégorie pour tenir compte de sa moyenne."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "targ_enc = TargetEncoder(cols=columns, smoothing=8, min_samples_leaf=5).fit(X_train, y_train)\n",
    "\n",
    "X_train_te = targ_enc.transform(X_train.reset_index(drop=True))\n",
    "X_val_te = targ_enc.transform(X_val.reset_index(drop=True))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "me_logit_score = get_score(logit, X_train_te, y_train, X_val_te, y_val)\n",
    "print('Logistic Regression score with target encoding with regularization:', me_logit_score)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "me_rf_score = get_score(rf, X_train_te, y_train, X_val_te, y_val)\n",
    "print('Random Forest score with target encoding with regularization:', me_rf_score)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Une autre approche pour régulariser l'encodeur cible consiste à calculer la relation statistique entre chaque catégorie et la variable cible via une division kfold. Cette méthode n'est actuellement pas disponible dans l'implémentation category_encoders et doit être écrite à partir de zéro."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import KFold\n",
    "\n",
    "# Create 5 kfold splits:\n",
    "kf = KFold(random_state=17, n_splits=5, shuffle=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create copy of data:\n",
    "X_train_te = X_train.copy()\n",
    "X_train_te['target'] = y_train"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "all_set = []\n",
    "\n",
    "for train_index, val_index in kf.split(X_train_te):\n",
    "    # Create splits:\n",
    "    train, val = X_train_te.iloc[train_index], X_train_te.iloc[val_index]\n",
    "    val=val.copy()\n",
    "    \n",
    "    # Calculate the mean of each column:\n",
    "    means_list = []\n",
    "    for col in columns:\n",
    "        means_list.append(train.groupby(str(col)).target.mean())\n",
    "    \n",
    "    # Calculate the mean of each category in each column:\n",
    "    col_means = []\n",
    "    for means_series in means_list:\n",
    "        col_means.append(means_series.mean())\n",
    "    \n",
    "    # Encode the data:\n",
    "    for column, means_series, means in zip(columns, means_list, col_means):\n",
    "        val[str(column) + '_target_enc'] = val[str(column)].map(means_series).fillna(means) \n",
    "    \n",
    "    list_of_mean_enc = [str(column) + '_target_enc' for column in columns]\n",
    "    list_of_mean_enc.extend(columns)\n",
    "    \n",
    "    all_set.append(val[list_of_mean_enc].copy())\n",
    "\n",
    "X_train_te=pd.concat(all_set, axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Apply encodings to validation set:\n",
    "X_val_te = pd.DataFrame(index=X_val.index)\n",
    "for column, means in zip(columns, col_means):\n",
    "    enc_dict = X_train_te.groupby(column).mean().to_dict()[str(column) + '_target_enc']\n",
    "    X_val_te[column] = X_val[column].map(enc_dict).fillna(means)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create list of target encoded columns:\n",
    "list_of_target_enc = [str(column) + '_target_enc' for column in columns]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "kf_reg_logit_score = get_score(logit, X_train_te[list_of_target_enc], y_train, X_val_te, y_val)\n",
    "print('Logistic Regression score with kfold-regularized target encoding:', kf_reg_logit_score)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "kf_reg_rf_score = get_score(rf, X_train_te[list_of_target_enc], y_train, X_val_te, y_val)\n",
    "print('Random Forest score with kfold-regularized target encoding:', kf_reg_rf_score)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "en"
   },
   "source": [
    "## Weight Of Evidence (WOE):"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "L'encodeur WOE (Weight of evidence) calcule le logarithme naturel du% de non-événements divisé par le% d'événements pour chaque catégorie dans une caractéristique catégorielle. Pour plus de précision, les événements se réfèrent à la variable cible."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from category_encoders import WOEEncoder\n",
    "\n",
    "woe_enc = WOEEncoder(cols=columns, random_state=17).fit(X_train, y_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_woe = woe_enc.transform(X_train.reset_index(drop=True))\n",
    "X_val_woe = woe_enc.transform(X_val.reset_index(drop=True))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_woe.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "woe_logit_score = get_score(logit, X_train_woe, y_train, X_val_woe, y_val)\n",
    "print('Logistic Regression score with woe encoding:', woe_logit_score)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "woe_rf_score = get_score(rf, X_train_woe, y_train, X_val_woe, y_val)\n",
    "print('Random Forest score with woe encoding:', woe_rf_score)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "En résumé, les caractéristiques catégorielles peuvent être représentées de plus de façons que le traditionnel one-hot encoding. Ces représentations ont des effets différents sur nos modèles et le choix de la représentation est spécifique à la tâche (mission ou objectif). Le hachage des caractéristiques et l'encodage binaire nous offrent des moyens d'encoder les données avec des dimensions plus faibles, ce qui est moins coûteux en termes de calcul et mieux adapté aux modèles arborescents. Le Target Encoding et le Weight Of Evidence semblent être beaucoup plus spécifiques à la tâche.\n",
    "\n",
    "Vos commentaires seraient appréciés, ainsi que vos votes ! Merci."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### Pour en savoir plus:\n",
    "\n",
    "* [category_encoder documentation](http://contrib.scikit-learn.org/categorical-encoding/)\n",
    "* [weight of evidence](https://www.listendata.com/2015/03/weight-of-evidence-woe-and-information.html)\n",
    "* [smarter ways of encoding categorical data for machine learning](https://towardsdatascience.com/smarter-ways-to-encode-categorical-data-for-machine-learning-part-1-of-3-6dca2f71b159)\n",
    "* [an exploration of categorical variables](http://www.willmcginnis.com/2015/11/29/beyond-one-hot-an-exploration-of-categorical-variables/)"
   ]
  }
 ],
 "metadata": {
  "hide_input": false,
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.4"
  },
  "nbTranslate": {
   "displayLangs": [
    "*"
   ],
   "hotkey": "alt-t",
   "langInMainMenu": true,
   "sourceLang": "en",
   "targetLang": "fr",
   "useGoogleTranslate": true
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
